/*
 * wwan.c - 远程通信模块功能接口
 *
 * Last modified: 2022-1-6
 *        Author: Young Rong
 */

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<time.h>
#include<pthread.h>
#include<termios.h>
#include<fcntl.h>
#include<syslog.h>
#include "param.h"
#include "wwan.h"


char	imei[32];
char	iccid[32];
uint8_t	sq = 99;
int		ns;

static int      fd_atport = -1;
static uint8_t  flag_init;
static uint8_t  flag_online;
static uint8_t  flag_nowait;
static uint8_t  flag_needoff;
static uint8_t  count_redial;
static uint32_t count_restart;
static uint32_t count_ntp;
static uint32_t restart = 60;

manufacturer_name_type manufacturer_name;
net_status_error_type net_status_error;

static void module_pwrctrl(uint8_t on)
{
	int fd = open(syscfg.pwr_path, O_WRONLY);
	if(fd < 0)
			return;

	write(fd, on? "1" : "0", 1);
	close(fd);
}

static int wait_at_reply(int fd, char *expect)
{
	char	reply[128];
	uint8_t	i, k = 0;

	memset(reply, 0, sizeof(reply));
	for(i = 0; i < 50; i ++){
		k += read(fd, &reply[k], sizeof(reply) - strlen(reply) - 1);
		if(strstr(reply, expect))
			break;
		else if(strstr(reply, "ERROR"))
			return -1;
		else
			usleep(100000);
	}

	// printf("____________________\n");
	// for(int j=0; reply[j]!='\0'; j++)
	// {
	// 	printf("%c", reply[j]);
	// }

	if(i >= 50)
		return -1;

	return 0;
}

static int wait_at_reply2(int fd, char *expect1, char *expect2)
{
	char	reply[128];
	uint8_t	i, k = 0;

	memset(reply, 0, sizeof(reply));
	for(i = 0; i < 50; i ++){
		k += read(fd, &reply[k], sizeof(reply) - strlen(reply) - 1);
		if(strstr(reply, expect1) || strstr(reply, expect2))
			break;
		else if(strstr(reply, "ERROR"))
			return -1;
		else
			usleep(100000);
	}
	if(i >= 50)
		return -1;

	return 0;
}

static manufacturer_name_type wait_at_reply_ati(int fd)
{
	char	reply[128];
	uint8_t	i, k = 0;

	memset(reply, 0, sizeof(reply));
	for(i = 0; i < 50; i ++){
		k += read(fd, &reply[k], sizeof(reply) - strlen(reply) - 1);
		if(strstr(reply, "OK"))
			break;
		else
			usleep(100000);
	}
	if(i >= 50)
		return -1;

	if(strstr(reply, "Yuga"))
		manufacturer_name = Yuga;
	else if(strstr(reply, "Quectel"))
		manufacturer_name = Quectel;
	else if(strstr(reply, "AirM2M"))
		manufacturer_name = AirM2M;
	else if(strstr(reply, "CMCC"))
		manufacturer_name = CMCC;
	else
		manufacturer_name = Unknown;
	return manufacturer_name;
}

static int wait_at_reply_imei_cmcc(int fd, char *expect)
{
	char	reply[128], *p, *q;
	uint8_t	i, k = 0;

	memset(reply, 0, sizeof(reply));
	for(i = 0; i < 50; i ++){
		k += read(fd, &reply[k], sizeof(reply) - strlen(reply) - 1);
		if(strstr(reply, "OK"))
			break;
		else
			usleep(100000);
	}
	if(i >= 50)
		return -1;
	//syslog(LOG_ERR, "get CMCC IMEI success!");
	p = strstr(reply, "\r\n");
	// syslog(LOG_ERR, "p->%p",p);
	q = strstr(reply, "\r\n\r\nOK\r\n");
	// syslog(LOG_ERR, "q->%p",q);
	if(p && q){
		p += 3;
		*q = '\0';
		strcpy(expect, p);
	}else
		return -1;

	return 0;
}

static int wait_at_reply_imei(int fd, char *expect)
{
	char	reply[128], *p, *q;
	uint8_t	i, k = 0;

	memset(reply, 0, sizeof(reply));
	for(i = 0; i < 50; i ++){
		k += read(fd, &reply[k], sizeof(reply) - strlen(reply) - 1);
		if(strstr(reply, "OK"))
			break;
		else
			usleep(100000);
	}
	if(i >= 50)
		return -1;

	p = strstr(reply, "\r\r\n");
	q = strstr(reply, "\r\n\r\nOK\r\n");
	if(p && q){
		p += 3;
		*q = '\0';
		strcpy(expect, p);
	}else
		return -1;

	return 0;
}

static int wait_at_reply_iccid(int fd, char *expect)
{
	char	reply[128], *p, *q;
	uint8_t	i, k = 0;

	memset(reply, 0, sizeof(reply));
	for(i = 0; i < 50; i ++){
		k += read(fd, &reply[k], sizeof(reply) - strlen(reply) - 1);
		if(strstr(reply, "OK"))
			break;
		else
			usleep(100000);
	}
	if(i >= 50)
		return -1;

	p = strstr(reply, "\r\r\n+ICCID: ");
	q = strstr(reply, "\r\n\r\nOK\r\n");
	if(p && q){
		p += 11;
		*q = '\0';
		strcpy(expect, p);
	}else
		return -1;

	return 0;
}

static uint8_t wait_at_reply_sq(int fd)
{
	uint32_t rssi = 0, ber;
	char	 reply[128], *p;
	uint8_t	 i, k = 0;

	memset(reply, 0, sizeof(reply));
	for(i = 0; i < 50; i ++){
		k += read(fd, &reply[k], sizeof(reply) - strlen(reply) - 1);
		if((p = strstr(reply, "+CSQ: ")))
			break;
		else
			usleep(100000);
	}
	if(i >= 50)
		return -1;

	sscanf(p + 6, "%u,%u", &rssi, &ber);
	return rssi;
}

static int wwan_init()
{
	struct termios opt;	//进行终端控制，定义了终端的各种属性和控制信息。 terminal i/o settings
	/*
	*struct termios {
    *	tcflag_t c_iflag;   // 输入模式标志
    *	tcflag_t c_oflag;   // 输出模式标志
    *	tcflag_t c_cflag;   // 控制模式标志
    *	tcflag_t c_lflag;   // 本地模式标志
    *	cc_t c_cc[NCCS];    // 控制字符数组
	*};
	*/

	manufacturer_name_type	ret;
	char	cmd[32];
	uint8_t i;

	manufacturer_name = Unknown;
	net_status_error = SUCCESS;
	/* 串口参数 */
	bzero(&opt, sizeof(opt));
	opt.c_cflag |= CLOCAL | CREAD;	//CLOCAL:终端设备不关心调制解调器的状态，可以独立工作。
									//CREAD :允许从终端设备读取输入数据。
	opt.c_cflag |= CS8;				//CS8   :设置终端设备的数据位数为8位。
	cfsetispeed(&opt, B115200);
	cfsetospeed(&opt, B115200);

	/* power on */
	module_pwrctrl(1);
	/* try to open AT port */
	for(i = 0; i < 30; i ++)
	{
		fd_atport = open(syscfg.at_path, O_RDWR | O_NOCTTY);//O_NOCTTY:打开终端设备时告诉系统不要将该终端设备分配为控制终端
		if(fd_atport < 0)
			sleep(1);
		else
		{
			//设置fd_atport属性为opt，等待所有输出完成后再改变属性。这确保在改变属性之前，所有之前的输出都已经被处理完毕。
			tcsetattr(fd_atport, TCSADRAIN, &opt);
			//刷新终端设备fd_atport的输入输出缓冲区
			tcflush(fd_atport, TCIOFLUSH);
			break;
		}
	}
	if(i >= 30){
		syslog(LOG_ERR, "AT port not found!");
		return -1;
	}
	/* test AT port */
	strcpy(cmd, "AT\r");
	for(i = 0; i < 5; i ++){
		write(fd_atport, cmd, strlen(cmd));
		if(!wait_at_reply(fd_atport, "OK"))
			break;
	}
	if(i >= 5)
	{
		syslog(LOG_ERR, "test AT port failed!");
		return -1;
	}

	strcpy(cmd, "AT+CGMI\r");	//查询制造商
	write(fd_atport, cmd, strlen(cmd));
	if((ret = wait_at_reply_ati(fd_atport)) < 0){
		syslog(LOG_ERR, "get modem vendor failed!");
		return -1;
	}
	switch(ret){
	case  Yuga:	
		syslog(LOG_INFO, "modem is Yuga.");
		break;
	case  Quectel:
		syslog(LOG_INFO, "modem is Quectel.");
		break;
	case  AirM2M:
		syslog(LOG_INFO, "modem is AirM2M.");
		break;
	case  CMCC:
		syslog(LOG_INFO, "modem is CMCC.");
		break;
	default:
		syslog(LOG_WARNING, "unknown modem vendor!");
	}

	/* read IMEI */
	if(CMCC == ret)
	{
		strcpy(cmd, "AT+CGSN=1\r");//获取国际移动设备识别码IMEI
		write(fd_atport, cmd, strlen(cmd));
		if(wait_at_reply_imei_cmcc(fd_atport, imei) < 0)
		{
			syslog(LOG_ERR, "read IMEI failed!");
			return -1;
		}
	}
	else
	{
		strcpy(cmd, "AT+GSN\r");//获取国际移动设备识别码IMEI
		write(fd_atport, cmd, strlen(cmd));
		if(wait_at_reply_imei(fd_atport, imei) < 0)
		{
			syslog(LOG_ERR, "read IMEI failed!");
			return -1;
		}
	}
	syslog(LOG_INFO, "IMEI: %s", imei);

	if(manufacturer_name == Quectel){
		strcpy(syscfg.card, "usb0");
		/* switch to RNDIS mode */
		strcpy(cmd, "AT+QCFG=\"usbnet\",3\r");
		write(fd_atport, cmd, strlen(cmd));
		if(wait_at_reply(fd_atport, "OK") < 0){
			syslog(LOG_ERR, "quectel switch to RNDIS mode failed!");
			return -1;
		}
	}
	// if(manufacturer_name = CMCC)
	// 	strcpy(syscfg.card, "usb0");

	// else if(modem_airm2m){
	// 	/* switch to RNDIS mode */
	// 	strcpy(cmd, "AT+SETUSB=1\r");
	// 	write(fd_atport, cmd, strlen(cmd));
	// 	if(wait_at_reply(fd_atport, "OK") < 0){
	// 		syslog(LOG_ERR, "airm2m switch to RNDIS mode failed!");
	// 		return -1;
	// 	}
	// }

	flag_init = 1;
	syslog(LOG_INFO, "module is ready.");

	return 0;
}

static int wait_at_reply_cpin(int fd, char *expect)
{
	char	reply[128];
	uint8_t	i, k = 0;

	memset(reply, 0, sizeof(reply));
	for(i = 0; i < 50; i ++){
		k += read(fd, &reply[k], sizeof(reply) - strlen(reply) - 1);
		printf("%c", reply[k-1]);
		if(strstr(reply, expect))
			break;
		else if(strstr(reply, "ERROR"))
			return -1;
		else
			usleep(100000);
	}
	printf("\n");

	if(i >= 50)
		return -1;

	return 0;
}

static int wwan_getns()
{
	char cmd[32];

	/* check SIM card */
	strcpy(cmd, "AT+CPIN?\r");
	write(fd_atport, cmd, strlen(cmd));
	if(wait_at_reply_cpin(fd_atport, "OK") < 0)
		net_status_error = PIN_CODE;

	/* check register status */
	if(CMCC == manufacturer_name)
		strcpy(cmd, "AT+CEREG?\r");
	else
		strcpy(cmd, "AT+CGREG?\r");	
	write(fd_atport, cmd, strlen(cmd));
	if(wait_at_reply2(fd_atport, ",1", ",5") < 0)
		net_status_error = GPRS_REGISTER;

	/* check attach status */
	strcpy(cmd, "AT+CGATT?\r");
	write(fd_atport, cmd, strlen(cmd));
	if(wait_at_reply(fd_atport, "+CGATT: 1") < 0)
		net_status_error = GPRS_ATTECH;

	return net_status_error;
}

static uint8_t wwan_getsq()
{
	char cmd[32];

	strcpy(cmd, "AT+CSQ\r");
	write(fd_atport, cmd, strlen(cmd));
	return wait_at_reply_sq(fd_atport);
}

static int wwan_dial()
{
	int  ret;
	char cmd[32];

	switch (manufacturer_name)
	{
	case Yuga:
		strcpy(cmd, "AT+RNDISCALL=1\r");
		break;
	case Quectel:
		strcpy(cmd, "AT+QNETDEVCTL=1,1\r");	//parameter1：0 挂断； 1 连接一次  
		break;
	case AirM2M:
		strcpy(cmd, "AT+RNDISCALL=1,0\r");
		break;
	case CMCC:
		strcpy(cmd, "AT+MDIALUP=1,1\r");
		break;
	default:
		break;
	}

	write(fd_atport, cmd, strlen(cmd));
	if(wait_at_reply(fd_atport, "OK") < 0)
	{
		syslog(LOG_ERR, "dial-up failed!");
		return -1;
	}

	/* bring 'usb0' up */
	snprintf(cmd, sizeof(cmd), "ifconfig %s up", syscfg.card); //启用网络接口
	ret = system(cmd);
	if(ret){
		syslog(LOG_ERR, "bring '%s' up failed! err=%d", syscfg.card, ret);
		return -1;
	}
	/* start udhcpc */
	snprintf(cmd, sizeof(cmd), "udhcpc -i %s", syscfg.card);	//启动UDHCP客户端并为指定网络接口获取IP地址的命令
	ret = system(cmd);
	if(ret){
		syslog(LOG_ERR, "start udhcpc failed! err=%d", ret);
		return -1;
	}

	flag_online = 1;
	syslog(LOG_INFO, "module is online.");

	return 0;
}

uint8_t wwan_is_ok()
{
	return flag_init;
}

uint8_t wwan_is_online()
{
	return flag_online;
}

void wwan_offline()
{
	flag_needoff = 1;
}

void wwan_hangup()
{
	char cmd[32];

	switch (manufacturer_name)
	{
	case Yuga:
		strcpy(cmd, "AT+RNDISCALL=0\r");
		break;
	case Quectel:
		strcpy(cmd, "AT+QNETDEVCTL=0,1\r");	
		break;
	case AirM2M:
		strcpy(cmd, "AT+RNDISCALL=0,0\r");
		break;
	case CMCC:
		strcpy(cmd, "AT+MDIALUP=1,0\r");
		break;
	default:
		break;
	}

	write(fd_atport, cmd, strlen(cmd));
	if(wait_at_reply(fd_atport, "OK") < 0)
		syslog(LOG_ERR, "hangup failed!");

	system("killall udhcpc");
	snprintf(cmd, sizeof(cmd), "ifconfig %s down", syscfg.card);
	system(cmd);

	flag_online = 0;
	syslog(LOG_INFO, "module is offline.");
}

void wwan_shutdown()
{
	char	cmd[32];
	uint8_t i;

	if(fd_atport > 0){
		/* power down */
		switch (manufacturer_name)
		{
		case Yuga:
			strcpy(cmd, "AT$MYPOWEROFF\r");
			break;
		case Quectel:
			strcpy(cmd, "AT+QPOWD=1\r");	
			break;
		case AirM2M:
			strcpy(cmd, "AT+CPOWD=1\r");
			break;
		case CMCC:
			strcpy(cmd, "AT+CPOWD=1\r");
			break;
		default:
			break;
		}

		write(fd_atport, cmd, strlen(cmd));
		if(wait_at_reply(fd_atport, "OK") < 0)
			goto fin;
		/* wait powered down */
		for(i = 0; i < 150; i ++)
			if(access(syscfg.at_path, F_OK) < 0) //检查文件权限
			{
				//printf("unplugged\n");
				break;
			}
			else
				usleep(100000);
fin:
		/* close AT port */
		close(fd_atport);
	}
	module_pwrctrl(0);
	flag_init = 0;
	syslog(LOG_INFO, "module is powered off.");

	sleep(5);
}

void wwan_proc()
{
	char cmd[32];

	if(flag_init)
	{
		ns = wwan_getns();//net status?
		if(flag_online)
		{
			sq = wwan_getsq(); //signal quality
			if(ns || flag_needoff)
			{
				wwan_hangup();
				flag_needoff = 0;
			}else
			{
				if(!(count_ntp % 3600))
				{ /* NTP */
					char cmd[90];
					snprintf(cmd, sizeof(cmd), "ntpd -p %s -qN", syscfg.ntp_srv);
					system(cmd);//用于在程序中执行系统命令
					system("hwclock -w --utc");
				}
				else if(time(NULL) < 1603728000 && (count_ntp % 3600) > 15) /* NTP failed, retry... */
					count_ntp = 3599;
				count_ntp ++;

				sleep(1);
			}
		}else
		{    
			sq = 99;
			if(ns)
			{
				switch(ns)
				{
					case PIN_CODE: syslog(LOG_WARNING, "SIM card is not ready!");break; 	//允许程序向系统日志记录器发送消息
					case GPRS_REGISTER: syslog(LOG_WARNING, "register is not ready!");break;	//int priority 优先级
					case GPRS_ATTECH: syslog(LOG_WARNING, "attach is not ready!");break;		//const char *format 格式化字符串
					default: syslog(LOG_WARNING, "unknown status!");
				}
				sleep(5);

				count_restart += 5;
				if(count_restart >= restart)
				{
					wwan_shutdown();
					restart *= 2; /* double the restart time to prevent damage to the module */
				}
			}else
			{
				if(flag_nowait)
				{
					flag_nowait = 0;

					/* read ICCID */
					strcpy(cmd, "AT+ICCID\r");
					write(fd_atport, cmd, strlen(cmd));
					if(wait_at_reply_iccid(fd_atport, iccid) < 0)
						syslog(LOG_ERR, "read ICCID failed!");
					else
						syslog(LOG_INFO, "ICCID: %s", iccid);
				}else
					sleep(syscfg.wwan_redial);

				if(wwan_dial() < 0)
				{
					count_redial ++;
					if(count_redial >= 3)
					{
						count_redial = 0;
						wwan_shutdown();
					}
				}
			}
		}
	}else
	{
		sq = 99;
		if(wwan_init() < 0)
		{
			wwan_shutdown();
			sleep(10);
		}else
		{
			flag_nowait = 1;
			count_redial = 0;//redial 重拨
		}
	}
}
